package com.mzy.NetworkProgram.networkinterfacebasic.networkimagess;

import com.mzy.NetworkProgram.networkinterfacebasic.data.*;
import com.mzy.NetworkProgram.networkinterfacebasic.utils.JavaCollectUtils;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import oshi.SystemInfo;
import oshi.hardware.CentralProcessor;
import oshi.hardware.HardwareAbstractionLayer;
import oshi.hardware.NetworkIF;
import oshi.hardware.common.AbstractNetworkIF;
import oshi.hardware.platform.linux.LinuxNetworkIF;
import oshi.software.os.OSFileStore;
import oshi.software.os.OSProcess;
import oshi.software.os.OperatingSystem;
import oshi.util.FileUtil;
import oshi.util.FormatUtil;

import java.io.File;
import java.net.NetworkInterface;
import java.time.Instant;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @ClassName
 * @Description
 * @Author dyf
 * @Date 2021/4/21
 * @Version 1.0
 */
@Slf4j
public class OshiCollectUtils {


    private OshiCollectUtils() {
        throw new IllegalStateException("Utility class");
    }



    public static final SystemInfo SI = new SystemInfo();
    public static final HardwareAbstractionLayer HAL = SI.getHardware();
    public static final OperatingSystem OS = SI.getOperatingSystem();
    public static final CentralProcessor PROCESSOR = HAL.getProcessor();


    public static List<ProcessInfo> getProcessInfos(int limit) {
        List<ProcessInfo> list = new ArrayList<>();
        limit = limit >= 0 ? limit : OS.getProcessCount();
        List<OSProcess> osProcesses = OS.getProcesses(null, OperatingSystem.ProcessSorting.CPU_DESC, limit);
        long total = HAL.getMemory().getTotal();
        for (OSProcess osProcess : osProcesses) {
            ProcessInfo running = new ProcessInfo();
            running.setBits(osProcess.getBitness());
            running.setName(osProcess.getName());
            running.setOpenFiles(osProcess.getOpenFiles());
            running.setParentProcessId(osProcess.getParentProcessID());
            running.setPath(osProcess.getPath());
            running.setPriority(osProcess.getPriority());
            running.setProcessId(osProcess.getProcessID());
            running.setResidentSetSize(osProcess.getResidentSetSize());
            running.setVirtualSize(osProcess.getVirtualSize());
            running.setStartTime(osProcess.getStartTime());
            running.setState(osProcess.getState().name());
            running.setThreadCount(osProcess.getThreadCount());
            running.setKernelTime(osProcess.getKernelTime());
            running.setUserTime(osProcess.getUserTime());
            running.setUpTime(osProcess.getUpTime());
            running.setUser(osProcess.getUser());
            running.setUserId(osProcess.getUserID());
            running.setMemLoad((100d * osProcess.getResidentSetSize()) / total);
            list.add(running);
        }
        return list;
    }


    /**
     * linux 使用 非root 获取网卡信息时
     * OSHI 获取网卡类某个方法通过JNA 无法进行初始化UDEV 导致线程锁住
     * 使用包装类，剔除JNA 涉及到的方法
     */
    public static class WrapLinuxNetworkIF extends AbstractNetworkIF {

        private static final Logger LOG = LoggerFactory.getLogger(LinuxNetworkIF.class);

        private int ifType;
        private boolean connectorPresent;
        private long bytesRecv;
        private long bytesSent;
        private long packetsRecv;
        private long packetsSent;
        private long inErrors;
        private long outErrors;
        private long inDrops;
        private long collisions;
        private long speed;
        private long timeStamp;
        private String ifAlias;
        private IfOperStatus ifOperStatus;

        public WrapLinuxNetworkIF(NetworkInterface netint) throws InstantiationException {
            super(netint, netint.getDisplayName());
            updateAttributes();
        }


        /**
         * Gets network interfaces on this machine
         *
         * @param includeLocalInterfaces include local interfaces in the result
         * @return A list of {@link NetworkIF} objects representing the interfaces
         */
        public static List<NetworkIF> getNetworks(boolean includeLocalInterfaces) {
            List<NetworkIF> ifList = new ArrayList<>();
            for (NetworkInterface ni : getNetworkInterfaces(includeLocalInterfaces)) {
                try {
                    ifList.add(new WrapLinuxNetworkIF(ni));
                } catch (InstantiationException e) {
                    LOG.debug("Network Interface Instantiation failed: {}", e.getMessage());
                }
            }
            return ifList;
        }

        @Override
        public int getIfType() {
            return this.ifType;
        }

        @Override
        public boolean isConnectorPresent() {
            return this.connectorPresent;
        }

        @Override
        public long getBytesRecv() {
            return this.bytesRecv;
        }

        @Override
        public long getBytesSent() {
            return this.bytesSent;
        }

        @Override
        public long getPacketsRecv() {
            return this.packetsRecv;
        }

        @Override
        public long getPacketsSent() {
            return this.packetsSent;
        }

        @Override
        public long getInErrors() {
            return this.inErrors;
        }

        @Override
        public long getOutErrors() {
            return this.outErrors;
        }

        @Override
        public long getInDrops() {
            return this.inDrops;
        }

        @Override
        public long getCollisions() {
            return this.collisions;
        }

        @Override
        public long getSpeed() {
            return this.speed;
        }

        @Override
        public long getTimeStamp() {
            return this.timeStamp;
        }

        @Override
        public String getIfAlias() {
            return ifAlias;
        }

        @Override
        public IfOperStatus getIfOperStatus() {
            return ifOperStatus;
        }

        @Override
        public boolean updateAttributes() {
            try {
                File ifDir = new File(String.format("/sys/class/net/%s/statistics", getName()));
                if (!ifDir.isDirectory()) {
                    return false;
                }
            } catch (SecurityException e) {
                return false;
            }
            String ifTypePath = String.format("/sys/class/net/%s/type", getName());
            String carrierPath = String.format("/sys/class/net/%s/carrier", getName());
            String txBytesPath = String.format("/sys/class/net/%s/statistics/tx_bytes", getName());
            String rxBytesPath = String.format("/sys/class/net/%s/statistics/rx_bytes", getName());
            String txPacketsPath = String.format("/sys/class/net/%s/statistics/tx_packets", getName());
            String rxPacketsPath = String.format("/sys/class/net/%s/statistics/rx_packets", getName());
            String txErrorsPath = String.format("/sys/class/net/%s/statistics/tx_errors", getName());
            String rxErrorsPath = String.format("/sys/class/net/%s/statistics/rx_errors", getName());
            String collisionsPath = String.format("/sys/class/net/%s/statistics/collisions", getName());
            String rxDropsPath = String.format("/sys/class/net/%s/statistics/rx_dropped", getName());
            String ifSpeed = String.format("/sys/class/net/%s/speed", getName());
            String ifAliasPath = String.format("/sys/class/net/%s/ifalias", getName());
            String ifOperStatusPath = String.format("/sys/class/net/%s/operstate", getName());

            this.timeStamp = System.currentTimeMillis();
            this.ifType = FileUtil.getIntFromFile(ifTypePath);
            this.connectorPresent = FileUtil.getIntFromFile(carrierPath) > 0;
            this.bytesSent = FileUtil.getUnsignedLongFromFile(txBytesPath);
            this.bytesRecv = FileUtil.getUnsignedLongFromFile(rxBytesPath);
            this.packetsSent = FileUtil.getUnsignedLongFromFile(txPacketsPath);
            this.packetsRecv = FileUtil.getUnsignedLongFromFile(rxPacketsPath);
            this.outErrors = FileUtil.getUnsignedLongFromFile(txErrorsPath);
            this.inErrors = FileUtil.getUnsignedLongFromFile(rxErrorsPath);
            this.collisions = FileUtil.getUnsignedLongFromFile(collisionsPath);
            this.inDrops = FileUtil.getUnsignedLongFromFile(rxDropsPath);
            long speedMiB = FileUtil.getUnsignedLongFromFile(ifSpeed);
            // speed may be -1 from file.
            this.speed = speedMiB < 0 ? 0 : speedMiB << 20;
            this.ifAlias = FileUtil.getStringFromFile(ifAliasPath);
            this.ifOperStatus = parseIfOperStatus(FileUtil.getStringFromFile(ifOperStatusPath));

            return true;
        }

        private static IfOperStatus parseIfOperStatus(String operState) {
            switch (operState) {
                case "up":
                    return IfOperStatus.UP;
                case "down":
                    return IfOperStatus.DOWN;
                case "testing":
                    return IfOperStatus.TESTING;
                case "dormant":
                    return IfOperStatus.DORMANT;
                case "notpresent":
                    return IfOperStatus.NOT_PRESENT;
                case "lowerlayerdown":
                    return IfOperStatus.LOWER_LAYER_DOWN;
                case "unknown":
                default:
                    return IfOperStatus.UNKNOWN;
            }
        }
    }

    /**
     * 获取网卡信息
     *
     * @param networkInfoMap
     * @return
     */
    public static Map<String, NetworkInfo> getNetworkInfoMap(Map<String, NetworkInfo> networkInfoMap) {

        List<NetworkIF> networks;
        if (!Constants.IS_WIN && !OS.isElevated()) {//linux 下非root 用户使用替代方案解决JNA 无法初始化 udev 导致卡死问题
            networks = WrapLinuxNetworkIF.getNetworks(false);
        } else {
            networks = HAL.getNetworkIFs();
        }


        Map<String, NetworkInfo> map = new HashMap<>(networks.size());
        for (NetworkIF network : networks) {
            NetworkInfo running = new NetworkInfo();
            String name = network.getName();
            running.setDisplayName(network.getDisplayName());
            running.setMacaddr(network.getMacaddr());
            running.setName(name);
            running.setIPv4addr(network.getIPv4addr());
            running.setSubnetMasks(network.getSubnetMasks());
            running.setIPv6addr(network.getIPv6addr());
            running.setPrefixLengths(network.getPrefixLengths());
            running.setMtu(network.getMTU());
            running.setSpeed(network.getSpeed());

            running.setBytesRecv(network.getBytesRecv());
            running.setBytesSent(network.getBytesSent());
            running.setTimeStamp(network.getTimeStamp());

            if (networkInfoMap != null && networkInfoMap.containsKey(name)) {
                NetworkInfo prevRunning = networkInfoMap.get(name);

                long duration = network.getTimeStamp() - prevRunning.getTimeStamp();
                if (duration <= 5000) {
                    running.setBytesRecvSpeed((long) ((network.getBytesRecv() - prevRunning.getBytesRecv()) / (duration / 1000d)));
                    running.setBytesSentSpeed((long) ((network.getBytesSent() - prevRunning.getBytesSent()) / (duration / 1000d)));
                }
            }
            map.put(name, running);
        }
        return map;
    }

    public static List<FileSystemInfo> getFileSystemInfo() {
        List<FileSystemInfo> fileSystemInfos = new ArrayList<>();
        for (OSFileStore fs : OS.getFileSystem().getFileStores()) {
            FileSystemInfo fileSystemInfo = new FileSystemInfo();
            fileSystemInfo.setName(fs.getName());
            fileSystemInfo.setMount(fs.getMount());
            fileSystemInfo.setFreeSpace(fs.getUsableSpace());
            fileSystemInfo.setTotalSpace(fs.getTotalSpace());
            fileSystemInfo.setUsage((fs.getTotalSpace() - fs.getUsableSpace()) / (double) fs.getTotalSpace());
            fileSystemInfos.add(fileSystemInfo);
        }
        return fileSystemInfos;
    }

    public static CpuInfo getCpuInfo() {

        CpuInfo cpuInfo = new CpuInfo();

        cpuInfo.setSystemBootTime(Instant.ofEpochSecond(OS.getSystemBootTime()).toString());
        cpuInfo.setSystemUptime(FormatUtil.formatElapsedSecs(OS.getSystemUptime()));
        cpuInfo.setContextSwitches(PROCESSOR.getContextSwitches());//上下文切换数
        cpuInfo.setInterrupts(PROCESSOR.getInterrupts());//中断数
        cpuInfo.setStepping(PROCESSOR.getProcessorIdentifier().getStepping());//步进
        cpuInfo.setVendorFreq(PROCESSOR.getProcessorIdentifier().getVendorFreq());//默认主频
        cpuInfo.setCpu64bit(PROCESSOR.getProcessorIdentifier().isCpu64bit());//64位CPU
        cpuInfo.setPhysicalPackageCount(PROCESSOR.getPhysicalPackageCount());//物理插槽
        cpuInfo.setPhysicalProcessorCount(PROCESSOR.getPhysicalProcessorCount());//物理核心数
        cpuInfo.setLogicalProcessorCount(PROCESSOR.getLogicalProcessorCount());//逻辑核心数
        cpuInfo.setMaxFreq(PROCESSOR.getMaxFreq());//最大主频

        cpuInfo.setUsage(JavaCollectUtils.getCpuUsage());

        return cpuInfo;
    }

}
